/*
 * Copyright (c) 2007 Compl Yue Still, All Rights Reserved.
 */

package hbi.wire.java.host;

import hbi.wire.ProtocolError;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

class MetaHeader implements IncomingTag
{

    private static class AnnotationProxy implements InvocationHandler
    {
        private final Map<String, Object> valueMap;

        public AnnotationProxy(Map<String, Object> valueMap)
        {
            this.valueMap = valueMap;
        }

        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable
        {
            String key = method.getName();

            if (!valueMap.containsKey(key)) return method.getDefaultValue();

            Object value = valueMap.get(key);
            Class<?> returnType = method.getReturnType();
            if ((value == null && returnType.isPrimitive())
                || (value != null && !returnType.isInstance(value)))
            {
                throw new ClassCastException("Wrong metadata type: "
                    + (value == null ? "(null)" : value.getClass()
                        .getCanonicalName()) + " , expected: "
                    + returnType.getCanonicalName());
            }
            return value;
        }

    }

    private final TaskScript script;

    private final Map<String, Object> valueMap;

    public MetaHeader(TaskScript script) throws ProtocolError
    {
        this.script = script;
        this.valueMap = new HashMap<String, Object>();
    }

    private ResolvedType annotationType = null;

    private IncomingTag incomingTag = null;

    public boolean processIncomingData(ByteBuffer buffer) throws IOException
    {
        tag_scan: while (buffer.hasRemaining())
        {
            if (incomingTag == null)
            {
                if (annotationType == null)
                {
                    if (buffer.remaining() < 4) return false;
                    int typeIndex = buffer.getInt();
                    ResolvedType type = script.hostEnd
                        .getResolvedType(typeIndex);
                    if (!Annotation.class.isAssignableFrom(type.javaClass)
                        || !type.javaClass.isInterface())
                        throw new ProtocolError("Not an annotation type: "
                            + type.javaClass.getCanonicalName());
                    this.annotationType = type;
                    if (!buffer.hasRemaining()) break tag_scan;
                }
                int tag = buffer.get() & 0xFF;
                switch (tag)
                {
                case 'm': // method constant
                    incomingTag = new MethodConstant(this.annotationType);
                    break;
                case 'h': // meta method value
                    incomingTag = new MetaValue(this.annotationType,
                        this.valueMap);
                    break;
                case 'z': // finish meta header
                    script.headers.add((Annotation) Proxy.newProxyInstance(
                        annotationType.javaClass.getClassLoader(),
                        new Class<?>[] { annotationType.javaClass },
                        new AnnotationProxy(valueMap)));
                    return true;
                default:
                    throw new ProtocolError("Bad tag: " + tag);
                }
            }
            else if (incomingTag.processIncomingData(buffer))
                incomingTag = null;
            else
                break tag_scan;
        }
        return false;
    }

}
